package org.phalanx.gwt.client.graphics;

import java.util.ArrayList;
import java.util.Iterator;
import org.phalanx.gwt.client.graphics.map.MapCoordinates;
import org.phalanx.gwt.client.graphics.map.MapTile;
import org.phalanx.gwt.client.graphics.map.PhalanxMap;

import com.google.gwt.dom.client.Document;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.AbsolutePanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.widgetideas.client.GlassPanel;

public class PhalanxWebMapView extends AbsolutePanel implements PhalanxGraphicsConsole{
	protected PhalanxMap pMap;
	protected ArrayList<PhalanxMapEventListener> eventListenerList;
	protected Integer currentX = 0;
	protected Integer currentY = 0;
	private Double zoomLevel;
	private Integer width;
	private Integer length;
	private boolean isDrawBuildPoints = true;

	public static final Double ZOOM_DEFAULT = 1.0;
	public static final Double ZOOMIN_LEVEL_1 = 1.5;
	public static final Double ZOOMIN_LEVEL_2 = 2.0;
	public static final Double ZOOMOUT_LEVEL_1 = 0.75;
	public static final Double ZOOMOUT_LEVEL_2 = 0.5;
	public static final Double ZOOMOUT_LEVEL_3 = 0.25;


	public PhalanxWebMapView(PhalanxMap pMap, Integer l, Integer w){
		super();
		this.pMap = pMap;
		length = l;
		width = w;
		super.setSize(w + "px", l + "px");
		initializeMapViewProperties();
	}

	private void initializeMapViewProperties(){
		this.setZoomLevel(ZOOM_DEFAULT);

		// initialize listeners list
		eventListenerList = new ArrayList<PhalanxMapEventListener>();

		// Set allowed events
		sinkEvents(Event.ONCLICK);
		sinkEvents(Event.ONDBLCLICK);
		sinkEvents(Event.MOUSEEVENTS);
		sinkEvents(Event.FOCUSEVENTS);
		sinkEvents(Event.FOCUSEVENTS);
	}

	public Integer getTileSize(){
		return new Double(Math.floor(getActualTileSize()) * this.getZoomLevel()).intValue();
	}

	public Double getZoomLevel(){
		return zoomLevel;
	}

	public Integer getActualTileSize(){
		return pMap.getTileSize();
	}

	public void rehashImagesForZoom(Image newImage){
		/*		Collection<Image>s = tileTable.values();

		while(s.iterator().hasNext()){
			Image cachedImage = s.iterator().next();
		}*/

		newImage.setSize(this.getTileSize() + "px",this.getTileSize() + "px");
	}

	public PhalanxMap getMap(){
		return pMap;
	}

	public void setMap(PhalanxMap pMap){
		this.pMap = pMap;
	}

	public Integer getXPos(){
		return currentX;
	}

	public Integer getYPos(){
		return currentY;
	}

	public void setCoordinates(Integer newX, Integer newY){
		Integer mapLength = pMap.getMapLength();
		Integer mapWidth = pMap.getMapWidth();

		if (!(newX > mapWidth - 1 || newY > mapLength - 1 || newX < 0 || newY < 0)){
			currentX = newX;
			currentY = newY;
		}
	}


	public void setZoomLevel(Double zoom){
		if (zoom <= ZOOMIN_LEVEL_2 && zoom >= ZOOMOUT_LEVEL_3){
			this.zoomLevel = zoom;
		}
	}

	public Integer getPixelPointx(Integer tileX){
		int virtualX = (getCenterPosTile(getMapTileX())  - currentX) + tileX;
		return new Double(virtualX * getTileSize()).intValue();
	}

	public Integer getPixelPointy(Integer tileY){
		int virtualY = (getCenterPosTile(getMapTileY()) - currentY) + tileY;
		return new Double(virtualY * getTileSize()).intValue();
	}

	protected Integer getCenterPosTile(Integer w){
		return w / 2;
	}

	public MapCoordinates getMapTilePosition(Integer pX, Integer pY){
		MapCoordinates c = null;

		Integer getCenterPosW = getCenterPosTile(this.getMapTileX());
		Integer getCenterPosL = getCenterPosTile(this.getMapTileY());

		Integer xCoordinateAt0 = currentX - getCenterPosW;
		Integer yCoordinateAt0 = currentY - getCenterPosL;

		Integer virtualPositionX = pX / this.getTileSize();	
		Integer virtualPositionY = pY / this.getTileSize();

		c = new MapCoordinates(xCoordinateAt0 + virtualPositionX,yCoordinateAt0 + virtualPositionY);

		return c;
	}

	protected Integer getMapTileX(){
		Integer tileSize = getTileSize();
		Integer componentXSize = this.getWidth();

		Integer maxTilesX = roundUp(componentXSize.doubleValue() / tileSize.doubleValue());

		return maxTilesX;
	}

	protected Integer getMapTileY(){
		Integer tileSize = getTileSize();
		Integer componentYSize = this.getLength();

		Integer maxTilesY = roundUp(componentYSize.doubleValue() / tileSize.doubleValue());

		return maxTilesY;
	}

	public Integer getWidth(){
		return width;
	}

	public Integer getLength(){
		return length;
	}

	public void setViewSize(Integer l, Integer w){
		super.setSize(w + "px", l + "px");
		length = l;
		width = w;
	}

	private Integer roundUp(Double d){
		return new Double(Math.rint(d + 0.5f)).intValue();
	}

	public void renderMap(){
		this.clear();
		Integer getTileWidthCount = getMapTileX();
		Integer getTileLengthCount = getMapTileY();
		Integer getCenterPosW = getCenterPosTile(getTileWidthCount);
		Integer getCenterPosL = getCenterPosTile(getTileLengthCount);

		if ((this.getXPos() - getCenterPosW) <= 0){
			this.setCoordinates(getCenterPosW, this.getYPos());
		}
		if (this.getYPos() - getCenterPosL <= 0){
			this.setCoordinates(this.getXPos(), getCenterPosL);
		}
		int startPointY = currentY - getCenterPosL;
		int startPointX = currentX - getCenterPosW;

		int endPointY = startPointY + getTileLengthCount > this.getMap().getMapLength() ?
				this.getMap().getMapLength() : startPointY + getTileLengthCount;
				int endPointX = startPointX + getTileWidthCount > this.getMap().getMapWidth() ?
						this.getMap().getMapWidth() : startPointX + getTileWidthCount;

						for (int i = startPointY; i < endPointY; i++){
							for (int j = startPointX; j < endPointX; j++){
								drawTile(j ,i);
							}
						}
						
		if (isDrawBuildPoints){
			drawBuildPoints();
		}
	}

	private void drawTile(Integer x, Integer y){
		Integer pixelPointX = getPixelPointx(x);
		Integer pixelPointY = getPixelPointy(y);

		MapTile xyTile = this.getMap().getMapTileAt(new MapCoordinates(x, y));

		if (pMap.isBounds(new MapCoordinates(x,y))){
			if (xyTile == null){
				xyTile = this.getMap().getDefaultTile();
			}
			Image tempTileImage = new Image();
			//			Window.alert("1");
			String refString = "/PhalanxServlet/ServerInstance?context=mapresource&mapname=" + pMap.getMapName() 
			+ "&tilename=" + pMap.getMapTileSet().getTerrainRefNameFromTile(xyTile);
			//			Window.alert("2");
			tempTileImage.setUrl(refString);
			//			Window.alert("3");
			rehashImagesForZoom(tempTileImage);
			//			Window.alert("4");
			this.add(tempTileImage, pixelPointX, pixelPointY);
			//			Window.alert("5");
		}
	}

	private void drawBuildPoints(){
		// TODO: Figure out what we represent the 
		// build point with.

		Iterator<MapCoordinates> i = this.getMap().getBuildPoints().iterator();

		while (i.hasNext()){
			MapCoordinates c = i.next();

			if (pMap.isBounds(c)){
				Integer pixelPointX = getPixelPointx(c.getX());
				Integer pixelPointY = getPixelPointy(c.getY());

				Image tempTileImage = new Image();
				String refString = "/PhalanxServlet/ServerInstance?context=mapresource&bp=true";
				tempTileImage.setUrl(refString);
				rehashImagesForZoom(tempTileImage);
				this.add(tempTileImage, pixelPointX, pixelPointY);
			}
		}
	}

	@Override
	public void onBrowserEvent(Event event) {
		cycleThroughEventListeners(event);
		super.onBrowserEvent(event);
	}


	// These 2 functions are useful if any class wants to figure out
	// the pixel position relative to the page. This comes in to play
	// especially on a mouse event where the x and y are relative to the 
	// browser page and the coordinates for the view are needed.
	public Integer getRelativePixelPositionX(Integer xPos){
		return xPos - this.getAbsoluteLeft() + Document.get().getScrollLeft();
	}

	public Integer getRelativePixelPositionY(Integer yPos){
		return yPos - this.getAbsoluteTop() + Document.get().getScrollTop();
	}

	public void addPhalanxMapEventListener(PhalanxMapEventListener pmListener){
		if (!eventListenerList.contains(pmListener)){
			eventListenerList.add(pmListener);
		}
	}

	public void removePhalanxMapEventListener(PhalanxMapEventListener pmListener){
		if (eventListenerList.contains(pmListener)){
			eventListenerList.remove(pmListener);
		}
	}

	private void cycleThroughEventListeners(Event event){
		Iterator<PhalanxMapEventListener> i = eventListenerList.iterator();

		while(i.hasNext()){
			PhalanxMapEventListener listenerClass = i.next();

			listenerClass.onPhalanxMapTriggeredEvent(event, this);
		}
	}

	public void setDrawBuildPoints(boolean drawBuildPoints) {
		this.isDrawBuildPoints = drawBuildPoints;
	}

	public boolean isDrawBuildPoints() {
		return isDrawBuildPoints;
	}
}
